package com.atguigu.gulimall.product.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;
import com.atguigu.gulimall.product.dao.CategoryDao;
import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.atguigu.gulimall.product.service.CategoryBrandRelationService;
import com.atguigu.gulimall.product.service.CategoryService;
import com.atguigu.gulimall.product.vo.Catelog2Vo;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryEntity> listWithTree() {
        //1.查出所有分类
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        //2.组装成树形结构
        List<CategoryEntity> collect = categoryEntities.stream()
                //过滤出一级分类
                .filter(categoryEntity -> categoryEntity.getParentCid() == 0)
                .map(categoryEntity -> {
                    //递归查出所有子级分类
                    categoryEntity.setChildren(getChildrens(categoryEntity, categoryEntities));
                    return categoryEntity;
                }).sorted(//排序
                        Comparator.comparingInt(category -> (category.getSort() == null ? 0 : category.getSort()))
                )
                .collect(Collectors.toList());


        return collect;
    }

    @Override
    public List<Tree<Long>> listwithTreeUtil() {
        //1.查出所有分类
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        // 配置
        TreeNodeConfig config = new TreeNodeConfig();
        config.setIdKey("cat_id");//默认为id可以不设置
        config.setParentIdKey("parent_cid");//默认为parentId可以不设置
        config.setDeep(3);//最大递归深度
        config.setWeightKey("sort");//排序字段
        List<Tree<Long>> build = TreeUtil.build(categoryEntities, 0L, config, (object, tree) -> {
            tree.putExtra("cat_id", object.getCatId());
            tree.putExtra("parent_cid", object.getParentCid());
            tree.putExtra("icon", object.getIcon());
            tree.putExtra("name", object.getName());
            tree.putExtra("cat_level", object.getCatLevel());
            tree.putExtra("product_count", object.getProductCount());
            tree.putExtra("sort", object.getSort());

        });
        return build;
    }

    //代表当前方法的结果需要缓存，如果缓存中有方法不需要调用，如果没有，调用方法，将结果放入缓存
    //需要设置缓存别名
    @Cacheable(value = "category", key = "#root.methodName")
    @Override
    public List<CategoryEntity> getLevel1Category() {
        System.out.println("getLevel1Category...");
        List<CategoryEntity> listLevel1 = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0L));
        return listLevel1;
    }

    private List<CategoryEntity> getLevelList(List<CategoryEntity> allList, Long catId) {
        List<CategoryEntity> list = allList.stream().filter(e -> e.getParentCid().longValue() == catId).collect(Collectors.toList());
        return list;
    }

    @Override
    public Map<String, List<Catelog2Vo>> getCatelogJson() {
        //1。加入缓存
        String catelogJson = redisTemplate.opsForValue().get("catelogJson");
        Map<String, List<Catelog2Vo>> catelogJsonFromDB = null;
        if (StrUtil.isEmpty(catelogJson)) {
            //2.缓存没有查询数据库
            catelogJsonFromDB = getCatelogJsonFromDBWithRedissonLock();

            return catelogJsonFromDB;
        }
        catelogJsonFromDB = JSONUtil.toBean(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        }, true);
        return catelogJsonFromDB;

    }


    /**
     * 自己用redis写分布式锁
     */
    public Map<String, List<Catelog2Vo>> getCatelogJsonFromDBWithRedisLock() {
        //1.占分布式锁，去redis占坑
        //2.设置过期时间，即使有异常或者断电，也不会造成死锁,30s，和加锁是原子操作
        String uuid = UUID.randomUUID().toString();
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);

        if (lock) {
            //加锁成功，执行业务
            Map<String, List<Catelog2Vo>> dataFromDB;
            try {
                dataFromDB = getDataFromDB();
            } finally {
                //            String lockValue = redisTemplate.opsForValue().get("lock");
                //            if(lockValue.equals(uuid)){
                //                //删除自己的锁
                //                redisTemplate.delete("lock");//删除锁
                //            }
                //获取值对比+对比成功删除=原子操作，用lua脚本解锁
                String script = " if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }
            return dataFromDB;
        } else {
            //加锁失败，重试
            return getCatelogJsonFromDBWithRedisLock();//自旋的方式
        }


    }

    /**
     * 本地锁，有多个产品微服务时不能锁住
     */
    public Map<String, List<Catelog2Vo>> getCatelogJsonFromDBWithLocalLock() {
        //加锁，springboot对象默认都是单实例
        synchronized (this) {
            //得到锁之后，再查缓存，没有才去数据库查询
            return getDataFromDB();
        }

    }

    /**
     * redisson官方   分布式锁
     */
    public Map<String, List<Catelog2Vo>> getCatelogJsonFromDBWithRedissonLock() {
        //1.占分布式锁，去redis占坑
        //2.设置过期时间30s，即使有异常或者断电，也不会造成死锁,和加锁是原子操作
        RLock rLock = redissonClient.getLock("CatelogJson-lock");
        rLock.lock();
        Map<String, List<Catelog2Vo>> dataFromDB;
        try {
            //加锁成功，执行业务
            dataFromDB = getDataFromDB();
        } finally {
            rLock.unlock();
        }
        return dataFromDB;
    }

    private Map<String, List<Catelog2Vo>> getDataFromDB() {
        //得到锁之后，再查缓存，没有才去数据库查询
        String catelogJson = redisTemplate.opsForValue().get("catelogJson");
        if (!StrUtil.isEmpty(catelogJson)) {
            Map<String, List<Catelog2Vo>> catelogJsonFromDB = JSONUtil.toBean(catelogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            }, true);
            return catelogJsonFromDB;
        }
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        //查出所有1级分类
        List<CategoryEntity> level1Category = getLevelList(categoryEntities, 0L);
        Map<String, List<Catelog2Vo>> map = level1Category.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                    //查询1级分类的所有2级分类
                    List<CategoryEntity> categoryLevel2List = getLevelList(categoryEntities, v.getCatId());
                    List<Catelog2Vo> catelog2VoList = new ArrayList<>();
                    if (!CollUtil.isEmpty(categoryLevel2List)) {
                        catelog2VoList = categoryLevel2List.stream().map(e -> {
                            Catelog2Vo catelog2Vo = new Catelog2Vo();
                            catelog2Vo.setCatalog1Id(e.getParentCid().toString());
                            catelog2Vo.setId(e.getCatId().toString());
                            catelog2Vo.setName(e.getName());
                            List<CategoryEntity> categoryLevel3List = getLevelList(categoryEntities, e.getCatId());
                            List<Catelog2Vo.Catalog3Vo> catalog3VoList = new ArrayList<>();
                            if (!CollUtil.isEmpty(categoryLevel3List)) {
                                catalog3VoList = categoryLevel3List.stream().map(categoryLevel3 -> {
                                    Catelog2Vo.Catalog3Vo catalog3Vo = new Catelog2Vo.Catalog3Vo();
                                    catalog3Vo.setCatalog2Id(categoryLevel3.getParentCid().toString());
                                    catalog3Vo.setId(categoryLevel3.getCatId().toString());
                                    catalog3Vo.setName(categoryLevel3.getName());
                                    return catalog3Vo;
                                }).collect(Collectors.toList());
                            }
                            catelog2Vo.setCatalog3List(catalog3VoList);

                            return catelog2Vo;
                        }).collect(Collectors.toList());
                    }
                    return catelog2VoList;
                }
        ));
        //3.查询到的数据放入缓存中应该在同一把锁中
        String jsonStr = JSONUtil.toJsonStr(map);
        redisTemplate.opsForValue().set("catelogJson", jsonStr, 1, TimeUnit.DAYS);
        return map;
    }

    private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) {
        List<CategoryEntity> children = all.stream()
                .filter(categoryEntity -> categoryEntity.getParentCid().equals(root.getCatId()))
                .map(categoryEntity -> {
                    //递归查出子级的子级
                    categoryEntity.setChildren(getChildrens(categoryEntity, all));
                    return categoryEntity;
                })
                .sorted(
                        Comparator.comparingInt(category -> (category.getSort() == null ? 0 : category.getSort()))
                )
                .collect(Collectors.toList());

        return children;
    }

    @Override
    public Long[] getCatelogPath(Long catelogId) {
        List<Long> list = new ArrayList<>();
        this.findCatelogPath(catelogId, list);
        Collections.reverse(list);
        return list.toArray(new Long[1]);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        if (!StrUtil.isEmpty(category.getName())) {
            categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());

            //Todo 其余与分类相关的
        }
        //更新redis缓存
        //双写模式，更新数据库后再更新redis

    }

    public List<Long> findCatelogPath(Long catelogId, List<Long> list) {
        list.add(catelogId);
        CategoryEntity categoryEntity = baseMapper.selectById(catelogId);
        if (categoryEntity.getParentCid() != 0) {
            findCatelogPath(categoryEntity.getParentCid(), list);
        }
        return list;
    }


}